Add a handler for servicing member-violations to the mv88e6xxx switch driver. When we receive a member-violation from the hardware first check the ATU for the corresponding entry and only service the interrupt if the ATU entry has a non-zero DPV and the new port that raised the interrupt is not in the DPV. Servicing this interrupt will send a switchdev notification for the new port. Signed-off-by: Elliot Ayrey <elliot.ayrey@xxxxxxxxxxxxxxxxxxx> --- drivers/net/dsa/mv88e6xxx/global1_atu.c | 38 +++++++++++++++++++++++++ drivers/net/dsa/mv88e6xxx/switchdev.c | 33 ++++++++++++++++++++- drivers/net/dsa/mv88e6xxx/switchdev.h | 2 ++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/mv88e6xxx/global1_atu.c b/drivers/net/dsa/mv88e6xxx/global1_atu.c index c47f068f56b3..5c5c53cb2ad0 100644 --- a/drivers/net/dsa/mv88e6xxx/global1_atu.c +++ b/drivers/net/dsa/mv88e6xxx/global1_atu.c @@ -399,12 +399,36 @@ int mv88e6xxx_g1_atu_remove(struct mv88e6xxx_chip *chip, u16 fid, int port, return mv88e6xxx_g1_atu_move(chip, fid, from_port, to_port, all); } +static int mv88e6xxx_g1_atu_entry_check(struct mv88e6xxx_chip *chip, u16 fid, u8 mac[ETH_ALEN], + bool *in_atu, u16 *dpv) +{ + struct mv88e6xxx_atu_entry entry; + int err; + + entry.state = 0; + ether_addr_copy(entry.mac, mac); + eth_addr_dec(entry.mac); + + mv88e6xxx_reg_lock(chip); + err = mv88e6xxx_g1_atu_getnext(chip, fid, &entry); + mv88e6xxx_reg_unlock(chip); + if (err) + return err; + + *in_atu = ether_addr_equal(entry.mac, mac); + if (dpv) + *dpv = entry.portvec; + + return err; +} + static irqreturn_t mv88e6xxx_g1_atu_prob_irq_thread_fn(int irq, void *dev_id) { struct mv88e6xxx_chip *chip = dev_id; struct mv88e6xxx_atu_entry entry; int err, spid; u16 val, fid; + bool in_atu = false; mv88e6xxx_reg_lock(chip); @@ -437,6 +461,20 @@ static irqreturn_t mv88e6xxx_g1_atu_prob_irq_thread_fn(int irq, void *dev_id) entry.portvec, entry.mac, fid); chip->ports[spid].atu_member_violation++; + + if (fid != MV88E6XXX_FID_STANDALONE && chip->ports[spid].mab) { + u16 dpv = 0; + + err = mv88e6xxx_g1_atu_entry_check(chip, fid, entry.mac, &in_atu, &dpv); + if (err) + goto out; + + if (in_atu && dpv != 0 && !(dpv & BIT(spid))) { + err = mv88e6xxx_handle_member_violation(chip, spid, &entry, fid); + if (err) + goto out; + } + } } if (val & MV88E6XXX_G1_ATU_OP_MISS_VIOLATION) { diff --git a/drivers/net/dsa/mv88e6xxx/switchdev.c b/drivers/net/dsa/mv88e6xxx/switchdev.c index 4c346a884fb2..88761677ff10 100644 --- a/drivers/net/dsa/mv88e6xxx/switchdev.c +++ b/drivers/net/dsa/mv88e6xxx/switchdev.c @@ -79,5 +79,36 @@ int mv88e6xxx_handle_miss_violation(struct mv88e6xxx_chip *chip, int port, brport, &info.info, NULL); rtnl_unlock(); - return err; + return notifier_to_errno(err); +} + +int mv88e6xxx_handle_member_violation(struct mv88e6xxx_chip *chip, int port, + struct mv88e6xxx_atu_entry *entry, u16 fid) +{ + struct switchdev_notifier_fdb_info info = { + .addr = entry->mac, + }; + struct net_device *brport; + struct dsa_port *dp; + u16 vid; + int err; + + err = mv88e6xxx_find_vid(chip, fid, &vid); + if (err) + return err; + + info.vid = vid; + dp = dsa_to_port(chip->ds, port); + + rtnl_lock(); + brport = dsa_port_to_bridge_port(dp); + if (!brport) { + rtnl_unlock(); + return -ENODEV; + } + err = call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE, + brport, &info.info, NULL); + rtnl_unlock(); + + return notifier_to_errno(err); } diff --git a/drivers/net/dsa/mv88e6xxx/switchdev.h b/drivers/net/dsa/mv88e6xxx/switchdev.h index 62214f9d62b0..f718dbfaf45d 100644 --- a/drivers/net/dsa/mv88e6xxx/switchdev.h +++ b/drivers/net/dsa/mv88e6xxx/switchdev.h @@ -15,5 +15,7 @@ int mv88e6xxx_handle_miss_violation(struct mv88e6xxx_chip *chip, int port, struct mv88e6xxx_atu_entry *entry, u16 fid); +int mv88e6xxx_handle_member_violation(struct mv88e6xxx_chip *chip, int port, + struct mv88e6xxx_atu_entry *entry, u16 fid); #endif /* _MV88E6XXX_SWITCHDEV_H_ */