Commit d7417ee9 authored by David S. Miller's avatar David S. Miller

Merge branch 'mv88e6xxx-offload-bridge-flags'

Tobias Waldekranz says:

====================
net: dsa: mv88e6xxx: Offload bridge port flags

Add support for offloading learning and broadcast flooding flags. With
this in place, mv88e6xx supports offloading of all bridge port flags
that are currently supported by the bridge.

Broadcast flooding is somewhat awkward to control as there is no
per-port bit for this like there is for unknown unicast and unknown
multicast. Instead we have to update the ATU entry for the broadcast
address for all currently used FIDs.

v2 -> v3:
  - Only return a netdev from dsa_port_to_bridge_port if the port is
    currently bridged (Vladimir & Florian)

v1 -> v2:
  - Ensure that mv88e6xxx_vtu_get handles VID 0 (Vladimir)
  - Fixed off-by-one in mv88e6xxx_port_set_assoc_vector (Vladimir)
  - Fast age all entries on port when disabling learning (Vladimir)
  - Correctly detect bridge flags on LAG ports (Vladimir)
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 44b958a6 8d1d8298
......@@ -1479,6 +1479,13 @@ static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port)
struct mv88e6xxx_chip *chip = ds->priv;
int err;
if (dsa_to_port(ds, port)->lag_dev)
/* Hardware is incapable of fast-aging a LAG through a
* regular ATU move operation. Until we have something
* more fancy in place this is a no-op.
*/
return;
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_g1_atu_remove(chip, 0, port, false);
mv88e6xxx_reg_unlock(chip);
......@@ -1495,13 +1502,54 @@ static int mv88e6xxx_vtu_setup(struct mv88e6xxx_chip *chip)
return mv88e6xxx_g1_vtu_flush(chip);
}
static int mv88e6xxx_vtu_getnext(struct mv88e6xxx_chip *chip,
static int mv88e6xxx_vtu_get(struct mv88e6xxx_chip *chip, u16 vid,
struct mv88e6xxx_vtu_entry *entry)
{
int err;
if (!chip->info->ops->vtu_getnext)
return -EOPNOTSUPP;
return chip->info->ops->vtu_getnext(chip, entry);
entry->vid = vid ? vid - 1 : mv88e6xxx_max_vid(chip);
entry->valid = false;
err = chip->info->ops->vtu_getnext(chip, entry);
if (entry->vid != vid)
entry->valid = false;
return err;
}
static int mv88e6xxx_vtu_walk(struct mv88e6xxx_chip *chip,
int (*cb)(struct mv88e6xxx_chip *chip,
const struct mv88e6xxx_vtu_entry *entry,
void *priv),
void *priv)
{
struct mv88e6xxx_vtu_entry entry = {
.vid = mv88e6xxx_max_vid(chip),
.valid = false,
};
int err;
if (!chip->info->ops->vtu_getnext)
return -EOPNOTSUPP;
do {
err = chip->info->ops->vtu_getnext(chip, &entry);
if (err)
return err;
if (!entry.valid)
break;
err = cb(chip, &entry, priv);
if (err)
return err;
} while (entry.vid < mv88e6xxx_max_vid(chip));
return 0;
}
static int mv88e6xxx_vtu_loadpurge(struct mv88e6xxx_chip *chip,
......@@ -1513,9 +1561,18 @@ static int mv88e6xxx_vtu_loadpurge(struct mv88e6xxx_chip *chip,
return chip->info->ops->vtu_loadpurge(chip, entry);
}
static int mv88e6xxx_fid_map_vlan(struct mv88e6xxx_chip *chip,
const struct mv88e6xxx_vtu_entry *entry,
void *_fid_bitmap)
{
unsigned long *fid_bitmap = _fid_bitmap;
set_bit(entry->fid, fid_bitmap);
return 0;
}
int mv88e6xxx_fid_map(struct mv88e6xxx_chip *chip, unsigned long *fid_bitmap)
{
struct mv88e6xxx_vtu_entry vlan;
int i, err;
u16 fid;
......@@ -1531,21 +1588,7 @@ int mv88e6xxx_fid_map(struct mv88e6xxx_chip *chip, unsigned long *fid_bitmap)
}
/* Set every FID bit used by the VLAN entries */
vlan.vid = mv88e6xxx_max_vid(chip);
vlan.valid = false;
do {
err = mv88e6xxx_vtu_getnext(chip, &vlan);
if (err)
return err;
if (!vlan.valid)
break;
set_bit(vlan.fid, fid_bitmap);
} while (vlan.vid < mv88e6xxx_max_vid(chip));
return 0;
return mv88e6xxx_vtu_walk(chip, mv88e6xxx_fid_map_vlan, fid_bitmap);
}
static int mv88e6xxx_atu_new(struct mv88e6xxx_chip *chip, u16 *fid)
......@@ -1582,19 +1625,13 @@ static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port,
if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port))
return 0;
vlan.vid = vid - 1;
vlan.valid = false;
err = mv88e6xxx_vtu_getnext(chip, &vlan);
err = mv88e6xxx_vtu_get(chip, vid, &vlan);
if (err)
return err;
if (!vlan.valid)
return 0;
if (vlan.vid != vid)
return 0;
for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
if (dsa_is_dsa_port(ds, i) || dsa_is_cpu_port(ds, i))
continue;
......@@ -1676,15 +1713,12 @@ static int mv88e6xxx_port_db_load_purge(struct mv88e6xxx_chip *chip, int port,
if (err)
return err;
} else {
vlan.vid = vid - 1;
vlan.valid = false;
err = mv88e6xxx_vtu_getnext(chip, &vlan);
err = mv88e6xxx_vtu_get(chip, vid, &vlan);
if (err)
return err;
/* switchdev expects -EOPNOTSUPP to honor software VLANs */
if (vlan.vid != vid || !vlan.valid)
if (!vlan.valid)
return -EOPNOTSUPP;
fid = vlan.fid;
......@@ -1934,8 +1968,10 @@ static int mv88e6xxx_set_rxnfc(struct dsa_switch *ds, int port,
static int mv88e6xxx_port_add_broadcast(struct mv88e6xxx_chip *chip, int port,
u16 vid)
{
const char broadcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
u8 state = MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC;
u8 broadcast[ETH_ALEN];
eth_broadcast_addr(broadcast);
return mv88e6xxx_port_db_load_purge(chip, port, broadcast, vid, state);
}
......@@ -1946,6 +1982,19 @@ static int mv88e6xxx_broadcast_setup(struct mv88e6xxx_chip *chip, u16 vid)
int err;
for (port = 0; port < mv88e6xxx_num_ports(chip); port++) {
struct dsa_port *dp = dsa_to_port(chip->ds, port);
struct net_device *brport;
if (dsa_is_unused_port(chip->ds, port))
continue;
brport = dsa_port_to_bridge_port(dp);
if (brport && !br_port_flag_is_set(brport, BR_BCAST_FLOOD))
/* Skip bridged user ports where broadcast
* flooding is disabled.
*/
continue;
err = mv88e6xxx_port_add_broadcast(chip, port, vid);
if (err)
return err;
......@@ -1954,6 +2003,53 @@ static int mv88e6xxx_broadcast_setup(struct mv88e6xxx_chip *chip, u16 vid)
return 0;
}
struct mv88e6xxx_port_broadcast_sync_ctx {
int port;
bool flood;
};
static int
mv88e6xxx_port_broadcast_sync_vlan(struct mv88e6xxx_chip *chip,
const struct mv88e6xxx_vtu_entry *vlan,
void *_ctx)
{
struct mv88e6xxx_port_broadcast_sync_ctx *ctx = _ctx;
u8 broadcast[ETH_ALEN];
u8 state;
if (ctx->flood)
state = MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC;
else
state = MV88E6XXX_G1_ATU_DATA_STATE_MC_UNUSED;
eth_broadcast_addr(broadcast);
return mv88e6xxx_port_db_load_purge(chip, ctx->port, broadcast,
vlan->vid, state);
}
static int mv88e6xxx_port_broadcast_sync(struct mv88e6xxx_chip *chip, int port,
bool flood)
{
struct mv88e6xxx_port_broadcast_sync_ctx ctx = {
.port = port,
.flood = flood,
};
struct mv88e6xxx_vtu_entry vid0 = {
.vid = 0,
};
int err;
/* Update the port's private database... */
err = mv88e6xxx_port_broadcast_sync_vlan(chip, &vid0, &ctx);
if (err)
return err;
/* ...and the database for all VLANs. */
return mv88e6xxx_vtu_walk(chip, mv88e6xxx_port_broadcast_sync_vlan,
&ctx);
}
static int mv88e6xxx_port_vlan_join(struct mv88e6xxx_chip *chip, int port,
u16 vid, u8 member, bool warn)
{
......@@ -1961,14 +2057,11 @@ static int mv88e6xxx_port_vlan_join(struct mv88e6xxx_chip *chip, int port,
struct mv88e6xxx_vtu_entry vlan;
int i, err;
vlan.vid = vid - 1;
vlan.valid = false;
err = mv88e6xxx_vtu_getnext(chip, &vlan);
err = mv88e6xxx_vtu_get(chip, vid, &vlan);
if (err)
return err;
if (vlan.vid != vid || !vlan.valid) {
if (!vlan.valid) {
memset(&vlan, 0, sizeof(vlan));
err = mv88e6xxx_atu_new(chip, &vlan.fid);
......@@ -2064,17 +2157,14 @@ static int mv88e6xxx_port_vlan_leave(struct mv88e6xxx_chip *chip,
if (!vid)
return -EOPNOTSUPP;
vlan.vid = vid - 1;
vlan.valid = false;
err = mv88e6xxx_vtu_getnext(chip, &vlan);
err = mv88e6xxx_vtu_get(chip, vid, &vlan);
if (err)
return err;
/* If the VLAN doesn't exist in hardware or the port isn't a member,
* tell switchdev that this VLAN is likely handled in software.
*/
if (vlan.vid != vid || !vlan.valid ||
if (!vlan.valid ||
vlan.member[port] == MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_NON_MEMBER)
return -EOPNOTSUPP;
......@@ -2191,10 +2281,30 @@ static int mv88e6xxx_port_db_dump_fid(struct mv88e6xxx_chip *chip,
return err;
}
struct mv88e6xxx_port_db_dump_vlan_ctx {
int port;
dsa_fdb_dump_cb_t *cb;
void *data;
};
static int mv88e6xxx_port_db_dump_vlan(struct mv88e6xxx_chip *chip,
const struct mv88e6xxx_vtu_entry *entry,
void *_data)
{
struct mv88e6xxx_port_db_dump_vlan_ctx *ctx = _data;
return mv88e6xxx_port_db_dump_fid(chip, entry->fid, entry->vid,
ctx->port, ctx->cb, ctx->data);
}
static int mv88e6xxx_port_db_dump(struct mv88e6xxx_chip *chip, int port,
dsa_fdb_dump_cb_t *cb, void *data)
{
struct mv88e6xxx_vtu_entry vlan;
struct mv88e6xxx_port_db_dump_vlan_ctx ctx = {
.port = port,
.cb = cb,
.data = data,
};
u16 fid;
int err;
......@@ -2207,25 +2317,7 @@ static int mv88e6xxx_port_db_dump(struct mv88e6xxx_chip *chip, int port,
if (err)
return err;
/* Dump VLANs' Filtering Information Databases */
vlan.vid = mv88e6xxx_max_vid(chip);
vlan.valid = false;
do {
err = mv88e6xxx_vtu_getnext(chip, &vlan);
if (err)
return err;
if (!vlan.valid)
break;
err = mv88e6xxx_port_db_dump_fid(chip, vlan.fid, vlan.vid, port,
cb, data);
if (err)
return err;
} while (vlan.vid < mv88e6xxx_max_vid(chip));
return err;
return mv88e6xxx_vtu_walk(chip, mv88e6xxx_port_db_dump_vlan, &ctx);
}
static int mv88e6xxx_port_fdb_dump(struct dsa_switch *ds, int port,
......@@ -2457,19 +2549,15 @@ static int mv88e6xxx_setup_message_port(struct mv88e6xxx_chip *chip, int port)
static int mv88e6xxx_setup_egress_floods(struct mv88e6xxx_chip *chip, int port)
{
struct dsa_switch *ds = chip->ds;
bool flood;
int err;
/* Upstream ports flood frames with unknown unicast or multicast DA */
flood = dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port);
if (chip->info->ops->port_set_ucast_flood) {
err = chip->info->ops->port_set_ucast_flood(chip, port, flood);
err = chip->info->ops->port_set_ucast_flood(chip, port, true);
if (err)
return err;
}
if (chip->info->ops->port_set_mcast_flood) {
err = chip->info->ops->port_set_mcast_flood(chip, port, flood);
err = chip->info->ops->port_set_mcast_flood(chip, port, true);
if (err)
return err;
}
......@@ -2712,15 +2800,20 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
return err;
}
/* Port Association Vector: when learning source addresses
* of packets, add the address to the address database using
* a port bitmap that has only the bit for this port set and
* the other bits clear.
/* Port Association Vector: disable automatic address learning
* on all user ports since they start out in standalone
* mode. When joining a bridge, learning will be configured to
* match the bridge port settings. Enable learning on all
* DSA/CPU ports. NOTE: FROM_CPU frames always bypass the
* learning process.
*
* Disable HoldAt1, IntOnAgeOut, LockedPort, IgnoreWrongData,
* and RefreshLocked. I.e. setup standard automatic learning.
*/
reg = 1 << port;
/* Disable learning for CPU port */
if (dsa_is_cpu_port(ds, port))
if (dsa_is_user_port(ds, port))
reg = 0;
else
reg = 1 << port;
err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_ASSOC_VECTOR,
reg);
......@@ -5576,7 +5669,8 @@ static int mv88e6xxx_port_pre_bridge_flags(struct dsa_switch *ds, int port,
struct mv88e6xxx_chip *chip = ds->priv;
const struct mv88e6xxx_ops *ops;
if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD))
if (flags.mask & ~(BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD |
BR_BCAST_FLOOD))
return -EINVAL;
ops = chip->info->ops;
......@@ -5595,10 +5689,23 @@ static int mv88e6xxx_port_bridge_flags(struct dsa_switch *ds, int port,
struct netlink_ext_ack *extack)
{
struct mv88e6xxx_chip *chip = ds->priv;
bool do_fast_age = false;
int err = -EOPNOTSUPP;
mv88e6xxx_reg_lock(chip);
if (flags.mask & BR_LEARNING) {
bool learning = !!(flags.val & BR_LEARNING);
u16 pav = learning ? (1 << port) : 0;
err = mv88e6xxx_port_set_assoc_vector(chip, port, pav);
if (err)
goto out;
if (!learning)
do_fast_age = true;
}
if (flags.mask & BR_FLOOD) {
bool unicast = !!(flags.val & BR_FLOOD);
......@@ -5617,9 +5724,20 @@ static int mv88e6xxx_port_bridge_flags(struct dsa_switch *ds, int port,
goto out;
}
if (flags.mask & BR_BCAST_FLOOD) {
bool broadcast = !!(flags.val & BR_BCAST_FLOOD);
err = mv88e6xxx_port_broadcast_sync(chip, port, broadcast);
if (err)
goto out;
}
out:
mv88e6xxx_reg_unlock(chip);
if (do_fast_age)
mv88e6xxx_port_fast_age(ds, port);
return err;
}
......
......@@ -1309,6 +1309,27 @@ int mv88e6097_port_egress_rate_limiting(struct mv88e6xxx_chip *chip, int port)
0x0001);
}
/* Offset 0x0B: Port Association Vector */
int mv88e6xxx_port_set_assoc_vector(struct mv88e6xxx_chip *chip, int port,
u16 pav)
{
u16 reg, mask;
int err;
err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_ASSOC_VECTOR,
&reg);
if (err)
return err;
mask = mv88e6xxx_port_mask(chip);
reg &= ~mask;
reg |= pav & mask;
return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_ASSOC_VECTOR,
reg);
}
/* Offset 0x0C: Port ATU Control */
int mv88e6xxx_port_disable_learn_limit(struct mv88e6xxx_chip *chip, int port)
......
......@@ -407,6 +407,8 @@ int mv88e6165_port_set_jumbo_size(struct mv88e6xxx_chip *chip, int port,
size_t size);
int mv88e6095_port_egress_rate_limiting(struct mv88e6xxx_chip *chip, int port);
int mv88e6097_port_egress_rate_limiting(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_port_set_assoc_vector(struct mv88e6xxx_chip *chip, int port,
u16 pav);
int mv88e6097_port_pause_limit(struct mv88e6xxx_chip *chip, int port, u8 in,
u8 out);
int mv88e6390_port_pause_limit(struct mv88e6xxx_chip *chip, int port, u8 in,
......
......@@ -493,6 +493,20 @@ static inline bool dsa_port_is_vlan_filtering(const struct dsa_port *dp)
return dp->vlan_filtering;
}
static inline
struct net_device *dsa_port_to_bridge_port(const struct dsa_port *dp)
{
if (!dp->bridge_dev)
return NULL;
if (dp->lag_dev)
return dp->lag_dev;
else if (dp->hsr_dev)
return dp->hsr_dev;
return dp->slave;
}
typedef int dsa_fdb_dump_cb_t(const unsigned char *addr, u16 vid,
bool is_static, void *data);
struct dsa_switch_ops {
......
......@@ -233,19 +233,7 @@ extern const struct phylink_mac_ops dsa_port_phylink_mac_ops;
static inline bool dsa_port_offloads_bridge_port(struct dsa_port *dp,
struct net_device *dev)
{
/* Switchdev offloading can be configured on: */
if (dev == dp->slave)
/* DSA ports directly connected to a bridge, and event
* was emitted for the ports themselves.
*/
return true;
if (dp->lag_dev == dev)
/* DSA ports connected to a bridge via a LAG */
return true;
return false;
return dsa_port_to_bridge_port(dp) == dev;
}
static inline bool dsa_port_offloads_bridge(struct dsa_port *dp,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment