diff mbox series

[net] net: dsa: mv88e6xxx: Verify after ATU Load ops

Message ID 20250304235352.3259613-1-Joseph.Huang@garmin.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series [net] net: dsa: mv88e6xxx: Verify after ATU Load ops | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for net
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag present in non-next series
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/build_tools success No tools touched, skip
netdev/cc_maintainers success CCed 7 of 7 maintainers
netdev/build_clang success Errors and warnings before: 0 this patch: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success Fixes tag looks correct
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/checkpatch warning WARNING: line length of 81 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/contest success net-next-2025-03-05--06-00 (tests: 894)

Commit Message

Joseph Huang March 4, 2025, 11:53 p.m. UTC
ATU Load operations could fail silently if there's not enough space
on the device to hold the new entry.

Do a Read-After-Write verification after each fdb/mdb add operation
to make sure that the operation was really successful, and return
-ENOSPC otherwise.

Fixes: defb05b9b9b4 ("net: dsa: mv88e6xxx: Add support for fdb_add, fdb_del, and fdb_getnext")
Signed-off-by: Joseph Huang <Joseph.Huang@garmin.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 52 +++++++++++++++++++++++---------
 1 file changed, 38 insertions(+), 14 deletions(-)

Comments

Andrew Lunn March 5, 2025, 3:14 p.m. UTC | #1
On Tue, Mar 04, 2025 at 06:53:51PM -0500, Joseph Huang wrote:
> ATU Load operations could fail silently if there's not enough space
> on the device to hold the new entry.
> 
> Do a Read-After-Write verification after each fdb/mdb add operation
> to make sure that the operation was really successful, and return
> -ENOSPC otherwise.

Please could you add a description of what the user sees when the ATU
is full. What makes this a bug which needs fixing? I would of thought
at least for unicast addresses, the switch has no entry for the
destination, so sends the packet to the CPU. The CPU will then
software bridge it out the correct port. Reporting ENOSPC will not
change that.

> @@ -2845,7 +2866,8 @@ static int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port,
>  
>  	mv88e6xxx_reg_lock(chip);
>  	err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid,
> -					   MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC);
> +					   MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC,
> +					   true);
>  	mv88e6xxx_reg_unlock(chip);
>  
>  	return err;

> @@ -6613,7 +6635,8 @@ static int mv88e6xxx_port_mdb_add(struct dsa_switch *ds, int port,
>  
>  	mv88e6xxx_reg_lock(chip);
>  	err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid,
> -					   MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC);
> +					   MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC,
> +					   true);
>  	mv88e6xxx_reg_unlock(chip);

This change seems bigger than what it needs to be. Rather than modify
mv88e6xxx_port_db_load_purge(), why not perform the lookup just in
these two functions via a helper?

    Andrew

---
pw-bot: cr
Joseph Huang March 5, 2025, 5:44 p.m. UTC | #2
On 3/5/2025 10:14 AM, Andrew Lunn wrote:
> On Tue, Mar 04, 2025 at 06:53:51PM -0500, Joseph Huang wrote:
>> ATU Load operations could fail silently if there's not enough space
>> on the device to hold the new entry.
>>
>> Do a Read-After-Write verification after each fdb/mdb add operation
>> to make sure that the operation was really successful, and return
>> -ENOSPC otherwise.
> 
> Please could you add a description of what the user sees when the ATU
> is full. What makes this a bug which needs fixing? I would of thought
> at least for unicast addresses, the switch has no entry for the
> destination, so sends the packet to the CPU. The CPU will then
> software bridge it out the correct port. Reporting ENOSPC will not
> change that.

Hi Andrew,

What the user will see when the ATU table is full depends on the unknown 
flood setting. If a user has unknown multicast flood disabled, what the 
user will see is that multicast packets are dropped when the ATU table 
is full. In other words, IGMP snooping is broken when the ATU Load 
operation fails silently.

Even if the packet is kicked up to the CPU and the CPU intends to 
forward the packet out via the software bridge, the forwarding attempt 
is going to be blocked due to the 'offload_fwd_mark' flag in 
nbp_switchdev_allowed_egress(). Even if that somehow worked, we will not 
be fully utilizing the hardware's switching capability and will be 
relying on the CPU to do the forwarding, which will likely result in 
lower throughput.

Reporting -ENOSPC will not change the fact that the ATU table is full, 
however it does give switchdev a chance to notify the user and then the 
user can take some further action accordingly. If nothing else, at least 
'bridge monitor' will now report that the entries are not offloaded.

Some other DSA drivers are reporting -ENOSPC as well when the table is 
full (at least b53 and ocelot).

> 
>> @@ -2845,7 +2866,8 @@ static int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port,
>>   
>>   	mv88e6xxx_reg_lock(chip);
>>   	err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid,
>> -					   MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC);
>> +					   MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC,
>> +					   true);
>>   	mv88e6xxx_reg_unlock(chip);
>>   
>>   	return err;
> 
>> @@ -6613,7 +6635,8 @@ static int mv88e6xxx_port_mdb_add(struct dsa_switch *ds, int port,
>>   
>>   	mv88e6xxx_reg_lock(chip);
>>   	err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid,
>> -					   MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC);
>> +					   MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC,
>> +					   true);
>>   	mv88e6xxx_reg_unlock(chip);
> 
> This change seems bigger than what it needs to be. Rather than modify
> mv88e6xxx_port_db_load_purge(), why not perform the lookup just in
> these two functions via a helper?
> 
>      Andrew

I will make that change. Thanks for the review.

Thanks,
Joseph
> 
> ---
> pw-bot: cr
diff mbox series

Patch

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 68d1e891752b..cb3c4ebc5534 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -2208,9 +2208,20 @@  mv88e6xxx_port_vlan_prepare(struct dsa_switch *ds, int port,
 	return err;
 }
 
+static int mv88e6xxx_port_db_get(struct mv88e6xxx_chip *chip,
+				 struct mv88e6xxx_atu_entry *entry,
+				 u16 fid, const unsigned char *addr)
+{
+	entry->state = 0;
+	ether_addr_copy(entry->mac, addr);
+	eth_addr_dec(entry->mac);
+
+	return mv88e6xxx_g1_atu_getnext(chip, fid, entry);
+}
+
 static int mv88e6xxx_port_db_load_purge(struct mv88e6xxx_chip *chip, int port,
 					const unsigned char *addr, u16 vid,
-					u8 state)
+					u8 state, bool verify)
 {
 	struct mv88e6xxx_atu_entry entry;
 	struct mv88e6xxx_vtu_entry vlan;
@@ -2238,11 +2249,7 @@  static int mv88e6xxx_port_db_load_purge(struct mv88e6xxx_chip *chip, int port,
 		fid = vlan.fid;
 	}
 
-	entry.state = 0;
-	ether_addr_copy(entry.mac, addr);
-	eth_addr_dec(entry.mac);
-
-	err = mv88e6xxx_g1_atu_getnext(chip, fid, &entry);
+	err = mv88e6xxx_port_db_get(chip, &entry, fid, addr);
 	if (err)
 		return err;
 
@@ -2266,7 +2273,20 @@  static int mv88e6xxx_port_db_load_purge(struct mv88e6xxx_chip *chip, int port,
 		entry.state = state;
 	}
 
-	return mv88e6xxx_g1_atu_loadpurge(chip, fid, &entry);
+	err = mv88e6xxx_g1_atu_loadpurge(chip, fid, &entry);
+	if (err)
+		return err;
+
+	if (!!state && verify) {
+		err = mv88e6xxx_port_db_get(chip, &entry, fid, addr);
+		if (err)
+			return err;
+
+		if (!entry.state || !ether_addr_equal(entry.mac, addr))
+			return -ENOSPC;
+	}
+
+	return 0;
 }
 
 static int mv88e6xxx_policy_apply(struct mv88e6xxx_chip *chip, int port,
@@ -2298,7 +2318,7 @@  static int mv88e6xxx_policy_apply(struct mv88e6xxx_chip *chip, int port,
 			return -EOPNOTSUPP;
 
 		err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid,
-						   state);
+						   state, false);
 		if (err)
 			return err;
 		break;
@@ -2487,7 +2507,8 @@  static int mv88e6xxx_port_add_broadcast(struct mv88e6xxx_chip *chip, int port,
 
 	eth_broadcast_addr(broadcast);
 
-	return mv88e6xxx_port_db_load_purge(chip, port, broadcast, vid, state);
+	return mv88e6xxx_port_db_load_purge(chip, port, broadcast, vid, state,
+					    false);
 }
 
 static int mv88e6xxx_broadcast_setup(struct mv88e6xxx_chip *chip, u16 vid)
@@ -2539,7 +2560,7 @@  mv88e6xxx_port_broadcast_sync_vlan(struct mv88e6xxx_chip *chip,
 	eth_broadcast_addr(broadcast);
 
 	return mv88e6xxx_port_db_load_purge(chip, ctx->port, broadcast,
-					    vlan->vid, state);
+					    vlan->vid, state, false);
 }
 
 static int mv88e6xxx_port_broadcast_sync(struct mv88e6xxx_chip *chip, int port,
@@ -2845,7 +2866,8 @@  static int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port,
 
 	mv88e6xxx_reg_lock(chip);
 	err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid,
-					   MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC);
+					   MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC,
+					   true);
 	mv88e6xxx_reg_unlock(chip);
 
 	return err;
@@ -2859,7 +2881,7 @@  static int mv88e6xxx_port_fdb_del(struct dsa_switch *ds, int port,
 	int err;
 
 	mv88e6xxx_reg_lock(chip);
-	err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid, 0);
+	err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid, 0, false);
 	mv88e6xxx_reg_unlock(chip);
 
 	return err;
@@ -6613,7 +6635,8 @@  static int mv88e6xxx_port_mdb_add(struct dsa_switch *ds, int port,
 
 	mv88e6xxx_reg_lock(chip);
 	err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid,
-					   MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC);
+					   MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC,
+					   true);
 	mv88e6xxx_reg_unlock(chip);
 
 	return err;
@@ -6627,7 +6650,8 @@  static int mv88e6xxx_port_mdb_del(struct dsa_switch *ds, int port,
 	int err;
 
 	mv88e6xxx_reg_lock(chip);
-	err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid, 0);
+	err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid, 0,
+					   false);
 	mv88e6xxx_reg_unlock(chip);
 
 	return err;