Message ID | 20220314095231.3486931-15-tobias@waldekranz.com (mailing list archive) |
---|---|
State | Superseded |
Delegated to: | Netdev Maintainers |
Headers | show |
Series | net: bridge: Multiple Spanning Trees | expand |
Context | Check | Description |
---|---|---|
netdev/tree_selection | success | Clearly marked for net-next |
netdev/fixes_present | success | Fixes tag not required for -next series |
netdev/subject_prefix | success | Link |
netdev/cover_letter | success | Series has a cover letter |
netdev/patch_count | success | Link |
netdev/header_inline | success | No static functions without inline keyword in header files |
netdev/build_32bit | success | Errors and warnings before: 0 this patch: 0 |
netdev/cc_maintainers | success | CCed 7 of 7 maintainers |
netdev/build_clang | success | Errors and warnings before: 0 this patch: 0 |
netdev/module_param | success | Was 0 now: 0 |
netdev/verify_signedoff | success | Signed-off-by tag matches author and committer |
netdev/verify_fixes | success | No Fixes tag |
netdev/build_allmodconfig_warn | success | Errors and warnings before: 0 this patch: 0 |
netdev/checkpatch | warning | WARNING: line length of 82 exceeds 80 columns |
netdev/kdoc | success | Errors and warnings before: 0 this patch: 0 |
netdev/source_inline | success | Was 0 now: 0 |
On Mon, Mar 14, 2022 at 10:52:31AM +0100, Tobias Waldekranz wrote: > Allocate a SID in the STU for each MSTID in use by a bridge and handle > the mapping of MSTIDs to VLANs using the SID field of each VTU entry. > > Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com> > --- > drivers/net/dsa/mv88e6xxx/chip.c | 251 ++++++++++++++++++++++++++++++- > drivers/net/dsa/mv88e6xxx/chip.h | 13 ++ > 2 files changed, 257 insertions(+), 7 deletions(-) > > diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c > index c14a62aa6a6c..c23dbf37aeec 100644 > --- a/drivers/net/dsa/mv88e6xxx/chip.c > +++ b/drivers/net/dsa/mv88e6xxx/chip.c > @@ -1667,24 +1667,32 @@ static int mv88e6xxx_pvt_setup(struct mv88e6xxx_chip *chip) > return 0; > } > > -static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port) > +static void mv88e6xxx_port_fast_age_fid(struct mv88e6xxx_chip *chip, int port, > + u16 fid) > { > - struct mv88e6xxx_chip *chip = ds->priv; > int err; > > - if (dsa_to_port(ds, port)->lag) > + if (dsa_to_port(chip->ds, port)->lag) > /* 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); > + err = mv88e6xxx_g1_atu_remove(chip, fid, port, false); > > if (err) > - dev_err(ds->dev, "p%d: failed to flush ATU\n", port); > + dev_err(chip->ds->dev, "p%d: failed to flush ATU (FID %u)\n", > + port, fid); > +} > + > +static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port) > +{ > + struct mv88e6xxx_chip *chip = ds->priv; > + > + mv88e6xxx_reg_lock(chip); > + mv88e6xxx_port_fast_age_fid(chip, port, 0); > + mv88e6xxx_reg_unlock(chip); > } > > static int mv88e6xxx_vtu_setup(struct mv88e6xxx_chip *chip) > @@ -1818,6 +1826,159 @@ static int mv88e6xxx_stu_setup(struct mv88e6xxx_chip *chip) > return mv88e6xxx_stu_loadpurge(chip, &stu); > } > > +static int mv88e6xxx_sid_get(struct mv88e6xxx_chip *chip, u8 *sid) > +{ > + DECLARE_BITMAP(busy, MV88E6XXX_N_SID) = { 0 }; > + struct mv88e6xxx_mst *mst; > + > + set_bit(0, busy); __set_bit > + > + list_for_each_entry(mst, &chip->msts, node) { > + set_bit(mst->stu.sid, busy); > + } Up to you, but parentheses are generally not used for single-line blocks. > + > + *sid = find_first_zero_bit(busy, MV88E6XXX_N_SID); > + > + return (*sid >= mv88e6xxx_max_sid(chip)) ? -ENOSPC : 0; > +} > + > +static int mv88e6xxx_mst_put(struct mv88e6xxx_chip *chip, u8 sid) > +{ > + struct mv88e6xxx_mst *mst, *tmp; > + int err; > + > + if (!sid) > + return 0; Very minor nitpick: since mv88e6xxx_mst_put already checks this, could you drop the "!sid" check from callers? > + > + list_for_each_entry_safe(mst, tmp, &chip->msts, node) { > + if (mst->stu.sid != sid) > + continue; > + > + if (!refcount_dec_and_test(&mst->refcnt)) > + return 0; > + > + mst->stu.valid = false; > + err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); > + if (err) Should we bother with a refcount_set(&mst->refcount, 1) on error? > + return err; > + > + list_del(&mst->node); > + kfree(mst); > + return 0; > + } > + > + return -ENOENT; > +} > + > +static int mv88e6xxx_mst_get(struct mv88e6xxx_chip *chip, struct net_device *br, > + u16 msti, u8 *sid) > +{ > + struct mv88e6xxx_mst *mst; > + int err, i; > + > + if (!mv88e6xxx_has_stu(chip)) { > + err = -EOPNOTSUPP; > + goto err; > + } > + > + if (!msti) { > + *sid = 0; > + return 0; > + } > + > + list_for_each_entry(mst, &chip->msts, node) { > + if (mst->br == br && mst->msti == msti) { > + refcount_inc(&mst->refcnt); > + *sid = mst->stu.sid; > + return 0; > + } > + } > + > + err = mv88e6xxx_sid_get(chip, sid); > + if (err) > + goto err; > + > + mst = kzalloc(sizeof(*mst), GFP_KERNEL); > + if (!mst) { > + err = -ENOMEM; > + goto err; > + } > + > + INIT_LIST_HEAD(&mst->node); > + refcount_set(&mst->refcnt, 1); > + mst->br = br; > + mst->msti = msti; > + mst->stu.valid = true; > + mst->stu.sid = *sid; > + > + /* The bridge starts out all ports in the disabled state. But > + * a STU state of disabled means to go by the port-global > + * state. So we set all user port's initial state to blocking, > + * to match the bridge's behavior. > + */ > + for (i = 0; i < mv88e6xxx_num_ports(chip); i++) > + mst->stu.state[i] = dsa_is_user_port(chip->ds, i) ? > + MV88E6XXX_PORT_CTL0_STATE_BLOCKING : > + MV88E6XXX_PORT_CTL0_STATE_DISABLED; > + > + err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); > + if (err) > + goto err_free; > + > + list_add_tail(&mst->node, &chip->msts); > + return 0; > + > +err_free: > + kfree(mst); > +err: > + return err; > +} > + > +static int mv88e6xxx_port_mst_state_set(struct dsa_switch *ds, int port, > + const struct switchdev_mst_state *st) > +{ > + struct dsa_port *dp = dsa_to_port(ds, port); > + struct mv88e6xxx_chip *chip = ds->priv; > + struct mv88e6xxx_mst *mst; > + u8 state; > + int err; > + > + if (!mv88e6xxx_has_stu(chip)) > + return -EOPNOTSUPP; > + > + switch (st->state) { > + case BR_STATE_DISABLED: > + case BR_STATE_BLOCKING: > + case BR_STATE_LISTENING: > + state = MV88E6XXX_PORT_CTL0_STATE_BLOCKING; > + break; > + case BR_STATE_LEARNING: > + state = MV88E6XXX_PORT_CTL0_STATE_LEARNING; > + break; > + case BR_STATE_FORWARDING: > + state = MV88E6XXX_PORT_CTL0_STATE_FORWARDING; > + break; > + default: > + return -EINVAL; > + } > + > + list_for_each_entry(mst, &chip->msts, node) { > + if (mst->br == dsa_port_bridge_dev_get(dp) && > + mst->msti == st->msti) { > + if (mst->stu.state[port] == state) > + return 0; > + > + mst->stu.state[port] = state; > + mv88e6xxx_reg_lock(chip); > + err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); > + mv88e6xxx_reg_unlock(chip); > + return err; > + } > + } > + > + return -ENOENT; > +} > + > static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port, > u16 vid) > { > @@ -2437,6 +2598,12 @@ static int mv88e6xxx_port_vlan_leave(struct mv88e6xxx_chip *chip, > if (err) > return err; > > + if (!vlan.valid && vlan.sid) { > + err = mv88e6xxx_mst_put(chip, vlan.sid); > + if (err) > + return err; > + } > + > return mv88e6xxx_g1_atu_remove(chip, vlan.fid, port, false); > } > > @@ -2482,6 +2649,72 @@ static int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, > return err; > } > > +static void mv88e6xxx_port_vlan_fast_age(struct dsa_switch *ds, int port, u16 vid) > +{ > + struct mv88e6xxx_chip *chip = ds->priv; > + struct mv88e6xxx_vtu_entry vlan; > + int err; > + > + mv88e6xxx_reg_lock(chip); > + > + err = mv88e6xxx_vtu_get(chip, vid, &vlan); > + if (err) > + goto unlock; > + > + mv88e6xxx_port_fast_age_fid(chip, port, vlan.fid); > + > +unlock: > + mv88e6xxx_reg_unlock(chip); > + > + if (err) > + dev_err(ds->dev, "p%d: failed to flush ATU in VID %u\n", > + port, vid); This error message actually corresponds to an mv88e6xxx_vtu_get() error, so the message is kind of incorrect. mv88e6xxx_port_fast_age_fid(), whose error code isn't propagated here, has its own "failed to flush ATU" error message. > +} Otherwise this looks pretty good.
On Mon, Mar 14, 2022 at 18:27, Vladimir Oltean <olteanv@gmail.com> wrote: > On Mon, Mar 14, 2022 at 10:52:31AM +0100, Tobias Waldekranz wrote: >> Allocate a SID in the STU for each MSTID in use by a bridge and handle >> the mapping of MSTIDs to VLANs using the SID field of each VTU entry. >> >> Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com> >> --- >> drivers/net/dsa/mv88e6xxx/chip.c | 251 ++++++++++++++++++++++++++++++- >> drivers/net/dsa/mv88e6xxx/chip.h | 13 ++ >> 2 files changed, 257 insertions(+), 7 deletions(-) >> >> diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c >> index c14a62aa6a6c..c23dbf37aeec 100644 >> --- a/drivers/net/dsa/mv88e6xxx/chip.c >> +++ b/drivers/net/dsa/mv88e6xxx/chip.c >> @@ -1667,24 +1667,32 @@ static int mv88e6xxx_pvt_setup(struct mv88e6xxx_chip *chip) >> return 0; >> } >> >> -static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port) >> +static void mv88e6xxx_port_fast_age_fid(struct mv88e6xxx_chip *chip, int port, >> + u16 fid) >> { >> - struct mv88e6xxx_chip *chip = ds->priv; >> int err; >> >> - if (dsa_to_port(ds, port)->lag) >> + if (dsa_to_port(chip->ds, port)->lag) >> /* 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); >> + err = mv88e6xxx_g1_atu_remove(chip, fid, port, false); >> >> if (err) >> - dev_err(ds->dev, "p%d: failed to flush ATU\n", port); >> + dev_err(chip->ds->dev, "p%d: failed to flush ATU (FID %u)\n", >> + port, fid); >> +} >> + >> +static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port) >> +{ >> + struct mv88e6xxx_chip *chip = ds->priv; >> + >> + mv88e6xxx_reg_lock(chip); >> + mv88e6xxx_port_fast_age_fid(chip, port, 0); >> + mv88e6xxx_reg_unlock(chip); >> } >> >> static int mv88e6xxx_vtu_setup(struct mv88e6xxx_chip *chip) >> @@ -1818,6 +1826,159 @@ static int mv88e6xxx_stu_setup(struct mv88e6xxx_chip *chip) >> return mv88e6xxx_stu_loadpurge(chip, &stu); >> } >> >> +static int mv88e6xxx_sid_get(struct mv88e6xxx_chip *chip, u8 *sid) >> +{ >> + DECLARE_BITMAP(busy, MV88E6XXX_N_SID) = { 0 }; >> + struct mv88e6xxx_mst *mst; >> + >> + set_bit(0, busy); > > __set_bit > Ack >> + >> + list_for_each_entry(mst, &chip->msts, node) { >> + set_bit(mst->stu.sid, busy); >> + } > > Up to you, but parentheses are generally not used for single-line blocks. > Ack >> + >> + *sid = find_first_zero_bit(busy, MV88E6XXX_N_SID); >> + >> + return (*sid >= mv88e6xxx_max_sid(chip)) ? -ENOSPC : 0; >> +} >> + >> +static int mv88e6xxx_mst_put(struct mv88e6xxx_chip *chip, u8 sid) >> +{ >> + struct mv88e6xxx_mst *mst, *tmp; >> + int err; >> + >> + if (!sid) >> + return 0; > > Very minor nitpick: since mv88e6xxx_mst_put already checks this, could > you drop the "!sid" check from callers? Dropping >> + >> + list_for_each_entry_safe(mst, tmp, &chip->msts, node) { >> + if (mst->stu.sid != sid) >> + continue; >> + >> + if (!refcount_dec_and_test(&mst->refcnt)) >> + return 0; >> + >> + mst->stu.valid = false; >> + err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); >> + if (err) > > Should we bother with a refcount_set(&mst->refcount, 1) on error? We might as well. Thanks. >> + return err; >> + >> + list_del(&mst->node); >> + kfree(mst); >> + return 0; >> + } >> + >> + return -ENOENT; >> +} >> + >> +static int mv88e6xxx_mst_get(struct mv88e6xxx_chip *chip, struct net_device *br, >> + u16 msti, u8 *sid) >> +{ >> + struct mv88e6xxx_mst *mst; >> + int err, i; >> + >> + if (!mv88e6xxx_has_stu(chip)) { >> + err = -EOPNOTSUPP; >> + goto err; >> + } >> + >> + if (!msti) { >> + *sid = 0; >> + return 0; >> + } >> + >> + list_for_each_entry(mst, &chip->msts, node) { >> + if (mst->br == br && mst->msti == msti) { >> + refcount_inc(&mst->refcnt); >> + *sid = mst->stu.sid; >> + return 0; >> + } >> + } >> + >> + err = mv88e6xxx_sid_get(chip, sid); >> + if (err) >> + goto err; >> + >> + mst = kzalloc(sizeof(*mst), GFP_KERNEL); >> + if (!mst) { >> + err = -ENOMEM; >> + goto err; >> + } >> + >> + INIT_LIST_HEAD(&mst->node); >> + refcount_set(&mst->refcnt, 1); >> + mst->br = br; >> + mst->msti = msti; >> + mst->stu.valid = true; >> + mst->stu.sid = *sid; >> + >> + /* The bridge starts out all ports in the disabled state. But >> + * a STU state of disabled means to go by the port-global >> + * state. So we set all user port's initial state to blocking, >> + * to match the bridge's behavior. >> + */ >> + for (i = 0; i < mv88e6xxx_num_ports(chip); i++) >> + mst->stu.state[i] = dsa_is_user_port(chip->ds, i) ? >> + MV88E6XXX_PORT_CTL0_STATE_BLOCKING : >> + MV88E6XXX_PORT_CTL0_STATE_DISABLED; >> + >> + err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); >> + if (err) >> + goto err_free; >> + >> + list_add_tail(&mst->node, &chip->msts); >> + return 0; >> + >> +err_free: >> + kfree(mst); >> +err: >> + return err; >> +} >> + >> +static int mv88e6xxx_port_mst_state_set(struct dsa_switch *ds, int port, >> + const struct switchdev_mst_state *st) >> +{ >> + struct dsa_port *dp = dsa_to_port(ds, port); >> + struct mv88e6xxx_chip *chip = ds->priv; >> + struct mv88e6xxx_mst *mst; >> + u8 state; >> + int err; >> + >> + if (!mv88e6xxx_has_stu(chip)) >> + return -EOPNOTSUPP; >> + >> + switch (st->state) { >> + case BR_STATE_DISABLED: >> + case BR_STATE_BLOCKING: >> + case BR_STATE_LISTENING: >> + state = MV88E6XXX_PORT_CTL0_STATE_BLOCKING; >> + break; >> + case BR_STATE_LEARNING: >> + state = MV88E6XXX_PORT_CTL0_STATE_LEARNING; >> + break; >> + case BR_STATE_FORWARDING: >> + state = MV88E6XXX_PORT_CTL0_STATE_FORWARDING; >> + break; >> + default: >> + return -EINVAL; >> + } >> + >> + list_for_each_entry(mst, &chip->msts, node) { >> + if (mst->br == dsa_port_bridge_dev_get(dp) && >> + mst->msti == st->msti) { >> + if (mst->stu.state[port] == state) >> + return 0; >> + >> + mst->stu.state[port] = state; >> + mv88e6xxx_reg_lock(chip); >> + err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); >> + mv88e6xxx_reg_unlock(chip); >> + return err; >> + } >> + } >> + >> + return -ENOENT; >> +} >> + >> static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port, >> u16 vid) >> { >> @@ -2437,6 +2598,12 @@ static int mv88e6xxx_port_vlan_leave(struct mv88e6xxx_chip *chip, >> if (err) >> return err; >> >> + if (!vlan.valid && vlan.sid) { >> + err = mv88e6xxx_mst_put(chip, vlan.sid); >> + if (err) >> + return err; >> + } >> + >> return mv88e6xxx_g1_atu_remove(chip, vlan.fid, port, false); >> } >> >> @@ -2482,6 +2649,72 @@ static int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, >> return err; >> } >> >> +static void mv88e6xxx_port_vlan_fast_age(struct dsa_switch *ds, int port, u16 vid) >> +{ >> + struct mv88e6xxx_chip *chip = ds->priv; >> + struct mv88e6xxx_vtu_entry vlan; >> + int err; >> + >> + mv88e6xxx_reg_lock(chip); >> + >> + err = mv88e6xxx_vtu_get(chip, vid, &vlan); >> + if (err) >> + goto unlock; >> + >> + mv88e6xxx_port_fast_age_fid(chip, port, vlan.fid); >> + >> +unlock: >> + mv88e6xxx_reg_unlock(chip); >> + >> + if (err) >> + dev_err(ds->dev, "p%d: failed to flush ATU in VID %u\n", >> + port, vid); > > This error message actually corresponds to an mv88e6xxx_vtu_get() error, > so the message is kind of incorrect. mv88e6xxx_port_fast_age_fid(), > whose error code isn't propagated here, has its own "failed to flush ATU" > error message. Not sure I agree. If this fails, the symptom will be lingering dynamic entries in the affected VLAN. In that case I think the current message, or something similar, will make it as easy as possible to establish a correlation. Yes, it failed because the VTU get op failed, but that is more of an internal affair in the driver. Anyway, it's a moot point, because I think we should just allow the error to bubble up to userspace instead - as you suggested in 11/14. >> +} > > Otherwise this looks pretty good. Careful now, don't spoil me :)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index c14a62aa6a6c..c23dbf37aeec 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -1667,24 +1667,32 @@ static int mv88e6xxx_pvt_setup(struct mv88e6xxx_chip *chip) return 0; } -static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port) +static void mv88e6xxx_port_fast_age_fid(struct mv88e6xxx_chip *chip, int port, + u16 fid) { - struct mv88e6xxx_chip *chip = ds->priv; int err; - if (dsa_to_port(ds, port)->lag) + if (dsa_to_port(chip->ds, port)->lag) /* 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); + err = mv88e6xxx_g1_atu_remove(chip, fid, port, false); if (err) - dev_err(ds->dev, "p%d: failed to flush ATU\n", port); + dev_err(chip->ds->dev, "p%d: failed to flush ATU (FID %u)\n", + port, fid); +} + +static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port) +{ + struct mv88e6xxx_chip *chip = ds->priv; + + mv88e6xxx_reg_lock(chip); + mv88e6xxx_port_fast_age_fid(chip, port, 0); + mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_vtu_setup(struct mv88e6xxx_chip *chip) @@ -1818,6 +1826,159 @@ static int mv88e6xxx_stu_setup(struct mv88e6xxx_chip *chip) return mv88e6xxx_stu_loadpurge(chip, &stu); } +static int mv88e6xxx_sid_get(struct mv88e6xxx_chip *chip, u8 *sid) +{ + DECLARE_BITMAP(busy, MV88E6XXX_N_SID) = { 0 }; + struct mv88e6xxx_mst *mst; + + set_bit(0, busy); + + list_for_each_entry(mst, &chip->msts, node) { + set_bit(mst->stu.sid, busy); + } + + *sid = find_first_zero_bit(busy, MV88E6XXX_N_SID); + + return (*sid >= mv88e6xxx_max_sid(chip)) ? -ENOSPC : 0; +} + +static int mv88e6xxx_mst_put(struct mv88e6xxx_chip *chip, u8 sid) +{ + struct mv88e6xxx_mst *mst, *tmp; + int err; + + if (!sid) + return 0; + + list_for_each_entry_safe(mst, tmp, &chip->msts, node) { + if (mst->stu.sid != sid) + continue; + + if (!refcount_dec_and_test(&mst->refcnt)) + return 0; + + mst->stu.valid = false; + err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); + if (err) + return err; + + list_del(&mst->node); + kfree(mst); + return 0; + } + + return -ENOENT; +} + +static int mv88e6xxx_mst_get(struct mv88e6xxx_chip *chip, struct net_device *br, + u16 msti, u8 *sid) +{ + struct mv88e6xxx_mst *mst; + int err, i; + + if (!mv88e6xxx_has_stu(chip)) { + err = -EOPNOTSUPP; + goto err; + } + + if (!msti) { + *sid = 0; + return 0; + } + + list_for_each_entry(mst, &chip->msts, node) { + if (mst->br == br && mst->msti == msti) { + refcount_inc(&mst->refcnt); + *sid = mst->stu.sid; + return 0; + } + } + + err = mv88e6xxx_sid_get(chip, sid); + if (err) + goto err; + + mst = kzalloc(sizeof(*mst), GFP_KERNEL); + if (!mst) { + err = -ENOMEM; + goto err; + } + + INIT_LIST_HEAD(&mst->node); + refcount_set(&mst->refcnt, 1); + mst->br = br; + mst->msti = msti; + mst->stu.valid = true; + mst->stu.sid = *sid; + + /* The bridge starts out all ports in the disabled state. But + * a STU state of disabled means to go by the port-global + * state. So we set all user port's initial state to blocking, + * to match the bridge's behavior. + */ + for (i = 0; i < mv88e6xxx_num_ports(chip); i++) + mst->stu.state[i] = dsa_is_user_port(chip->ds, i) ? + MV88E6XXX_PORT_CTL0_STATE_BLOCKING : + MV88E6XXX_PORT_CTL0_STATE_DISABLED; + + err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); + if (err) + goto err_free; + + list_add_tail(&mst->node, &chip->msts); + return 0; + +err_free: + kfree(mst); +err: + return err; +} + +static int mv88e6xxx_port_mst_state_set(struct dsa_switch *ds, int port, + const struct switchdev_mst_state *st) +{ + struct dsa_port *dp = dsa_to_port(ds, port); + struct mv88e6xxx_chip *chip = ds->priv; + struct mv88e6xxx_mst *mst; + u8 state; + int err; + + if (!mv88e6xxx_has_stu(chip)) + return -EOPNOTSUPP; + + switch (st->state) { + case BR_STATE_DISABLED: + case BR_STATE_BLOCKING: + case BR_STATE_LISTENING: + state = MV88E6XXX_PORT_CTL0_STATE_BLOCKING; + break; + case BR_STATE_LEARNING: + state = MV88E6XXX_PORT_CTL0_STATE_LEARNING; + break; + case BR_STATE_FORWARDING: + state = MV88E6XXX_PORT_CTL0_STATE_FORWARDING; + break; + default: + return -EINVAL; + } + + list_for_each_entry(mst, &chip->msts, node) { + if (mst->br == dsa_port_bridge_dev_get(dp) && + mst->msti == st->msti) { + if (mst->stu.state[port] == state) + return 0; + + mst->stu.state[port] = state; + mv88e6xxx_reg_lock(chip); + err = mv88e6xxx_stu_loadpurge(chip, &mst->stu); + mv88e6xxx_reg_unlock(chip); + return err; + } + } + + return -ENOENT; +} + static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port, u16 vid) { @@ -2437,6 +2598,12 @@ static int mv88e6xxx_port_vlan_leave(struct mv88e6xxx_chip *chip, if (err) return err; + if (!vlan.valid && vlan.sid) { + err = mv88e6xxx_mst_put(chip, vlan.sid); + if (err) + return err; + } + return mv88e6xxx_g1_atu_remove(chip, vlan.fid, port, false); } @@ -2482,6 +2649,72 @@ static int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, return err; } +static void mv88e6xxx_port_vlan_fast_age(struct dsa_switch *ds, int port, u16 vid) +{ + struct mv88e6xxx_chip *chip = ds->priv; + struct mv88e6xxx_vtu_entry vlan; + int err; + + mv88e6xxx_reg_lock(chip); + + err = mv88e6xxx_vtu_get(chip, vid, &vlan); + if (err) + goto unlock; + + mv88e6xxx_port_fast_age_fid(chip, port, vlan.fid); + +unlock: + mv88e6xxx_reg_unlock(chip); + + if (err) + dev_err(ds->dev, "p%d: failed to flush ATU in VID %u\n", + port, vid); +} + +static int mv88e6xxx_vlan_msti_set(struct dsa_switch *ds, + struct dsa_bridge bridge, + const struct switchdev_vlan_msti *msti) +{ + struct mv88e6xxx_chip *chip = ds->priv; + struct mv88e6xxx_vtu_entry vlan; + u8 old_sid, new_sid; + int err; + + mv88e6xxx_reg_lock(chip); + + err = mv88e6xxx_vtu_get(chip, msti->vid, &vlan); + if (err) + goto unlock; + + if (!vlan.valid) { + err = -EINVAL; + goto unlock; + } + + old_sid = vlan.sid; + + err = mv88e6xxx_mst_get(chip, bridge.dev, msti->msti, &new_sid); + if (err) + goto unlock; + + if (new_sid != old_sid) { + vlan.sid = new_sid; + + err = mv88e6xxx_vtu_loadpurge(chip, &vlan); + if (err) { + mv88e6xxx_mst_put(chip, new_sid); + goto unlock; + } + } + + if (old_sid) + err = mv88e6xxx_mst_put(chip, old_sid); + +unlock: + mv88e6xxx_reg_unlock(chip); + return err; +} + static int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port, const unsigned char *addr, u16 vid, struct dsa_db db) @@ -6008,6 +6241,7 @@ static struct mv88e6xxx_chip *mv88e6xxx_alloc_chip(struct device *dev) mutex_init(&chip->reg_lock); INIT_LIST_HEAD(&chip->mdios); idr_init(&chip->policies); + INIT_LIST_HEAD(&chip->msts); return chip; } @@ -6540,10 +6774,13 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = { .port_pre_bridge_flags = mv88e6xxx_port_pre_bridge_flags, .port_bridge_flags = mv88e6xxx_port_bridge_flags, .port_stp_state_set = mv88e6xxx_port_stp_state_set, + .port_mst_state_set = mv88e6xxx_port_mst_state_set, .port_fast_age = mv88e6xxx_port_fast_age, + .port_vlan_fast_age = mv88e6xxx_port_vlan_fast_age, .port_vlan_filtering = mv88e6xxx_port_vlan_filtering, .port_vlan_add = mv88e6xxx_port_vlan_add, .port_vlan_del = mv88e6xxx_port_vlan_del, + .vlan_msti_set = mv88e6xxx_vlan_msti_set, .port_fdb_add = mv88e6xxx_port_fdb_add, .port_fdb_del = mv88e6xxx_port_fdb_del, .port_fdb_dump = mv88e6xxx_port_fdb_dump, diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h index 6d4daa24d3e5..6a0b66354e1d 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.h +++ b/drivers/net/dsa/mv88e6xxx/chip.h @@ -297,6 +297,16 @@ struct mv88e6xxx_region_priv { enum mv88e6xxx_region_id id; }; +struct mv88e6xxx_mst { + struct list_head node; + + refcount_t refcnt; + struct net_device *br; + u16 msti; + + struct mv88e6xxx_stu_entry stu; +}; + struct mv88e6xxx_chip { const struct mv88e6xxx_info *info; @@ -397,6 +407,9 @@ struct mv88e6xxx_chip { /* devlink regions */ struct devlink_region *regions[_MV88E6XXX_REGION_MAX]; + + /* Bridge MST to SID mappings */ + struct list_head msts; }; struct mv88e6xxx_bus_ops {
Allocate a SID in the STU for each MSTID in use by a bridge and handle the mapping of MSTIDs to VLANs using the SID field of each VTU entry. Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com> --- drivers/net/dsa/mv88e6xxx/chip.c | 251 ++++++++++++++++++++++++++++++- drivers/net/dsa/mv88e6xxx/chip.h | 13 ++ 2 files changed, 257 insertions(+), 7 deletions(-)