forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
net: dsa: mv88e6xxxx: Add RMU functionality.
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
1 parent
61fe729
commit 5a8005d
Showing
5 changed files
with
386 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 *)ð->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; | ||
} |
Oops, something went wrong.